#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdbool.h>

/*
 * pthread Example copied from wikipedia
 * time measurement copied from here: http://www.tu-chemnitz.de/urz/kurse/unterlagen/C/kap3/zeitmess.htm
 * reentrant rand from here: http://stackoverflow.com/questions/10014981/using-drand48-r-as-random-number-generator-in-c
 * xmalloc found http://stackoverflow.com/questions/7590254/what-is-the-difference-between-xmalloc-and-malloc
 */
bool verbose = false;
void *GenerateRandomNumbers(void *argument) {
	long n, i;
	double x;

	// use drand48_r / srand48_r to have reentrant random generator
	// set seed
	struct drand48_data randBuffer;
	srand48_r(time(NULL), &randBuffer);

	n = *((long *) argument);
	if (verbose) {
		printf("generating %ld random variables\n", n);
	}
	for (i = 0; i < n; i++) {
		drand48_r(&randBuffer, &x);
	}
	if (verbose) {
		printf("done generating\n");
	}

	/* optionally: insert more useful stuff here */

	return NULL;
}

void getTime(struct timeval* begin) {
	if (gettimeofday(begin, (struct timezone *) 0)) {
		fprintf(stderr, "can not get time\n");
		exit(1);
	}
}

void calculateDifference(const struct timeval* begin, const struct timeval* end,
		long * seconds, long * useconds) {
	*seconds = end->tv_sec - begin->tv_sec;
	*useconds = end->tv_usec - begin->tv_usec;
	if (*useconds < 0) {
		*useconds += 1000000;
		(*seconds)--;
	}
}

void * xmalloc(size)
	unsigned size; {
	void *new_mem = (void *) malloc(size);
	if (new_mem == NULL) {
		fprintf(stderr, "fatal: memory exhausted (xmalloc of %u bytes).\n",
				size);
		exit(-1);
	}
	return new_mem;
}
// todo: i find it nicer to initialize the randBuffer seed outside of the thread and pass it to the GenerateRandomNumbers function
// todo: find a nicer timing method
int main(int argc, char **argv) {

	long t = 0, n = 0, nPerT = 0;
	int opt;
	int rc = 0, i = 0;
	int numCPU = 0;

	struct timeval begin, end;

	long seconds, useconds;

	while ((opt = getopt(argc, argv, "n:t:v")) != -1) {
		switch (opt) {
		case 'n':
			n = atoi(optarg);
			break;
		case 't':
			t = atoi(optarg);
			break;
		case 'v':
			verbose = 1;
			break;
		default: /* '?' */
			fprintf(stderr,
					"Usage: %s -n numberOfRandomVariables [-t number of threads]\n",
					argv[0]);
			exit(EXIT_FAILURE);
		}
	}

	if (n == 0) {
		fprintf(stderr,
				"Usage: %s -n numberOfRandomVariables [-t number of threads]\n",
				argv[0]);
		exit(EXIT_FAILURE);
	}

	numCPU = sysconf(_SC_NPROCESSORS_ONLN);
	if (t == 0) {
		t = numCPU;
	}
	if (verbose) {
		printf("T=%ld, N=%ld, numCPU=%d\n", t, n, numCPU);
	}
	getTime(&begin);

	pthread_t* threads = (pthread_t*) xmalloc(t * sizeof(pthread_t));

	nPerT = n / t;
	for (i = 0; i < t; ++i) {
		rc = pthread_create(&threads[i], NULL, GenerateRandomNumbers,
				(void *) &nPerT);
		assert(0 == rc);
	}

	/* wait for all threads to complete */
	for (i = 0; i < t; ++i) {
		rc = pthread_join(threads[i], NULL);
		assert(0 == rc);
	}

	free(threads);
	getTime(&end);
	calculateDifference(&begin, &end, &seconds, &useconds);
	if (verbose) {
		printf("done in %ld.%ld sec\n", seconds, useconds);
	} else {
		printf("%ld.%ld", seconds, useconds);
	}
	exit(EXIT_SUCCESS);
}

